﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;
using Elderos.Controls;
using Microsoft.Samples.CustomControls;
using TreeBuilder.Settings;

namespace TreeBuilder
{
	/// <summary>
	/// Interaction logic for TriangleLink.xaml
	/// </summary>
	public partial class TriangleLink : UserControl, IBinder, INameScope, IPrintable, IXmlSerializable
	{
		public INode From { get; set; }
		public INode To { get; set; }

		public TriangleLink()
		{
			InitializeComponent();
			Initialize(
				TriangleSettings.Default.Stroke,
				TriangleSettings.Default.Thickness,
				TriangleSettings.Default.DashStyle
				);
		}

		private void Initialize(Color color, double thickness, DoubleCollection dashArray)
		{
			Line.Stroke = new SolidColorBrush(color);
			Line.StrokeThickness = thickness;
			Line.StrokeDashArray = dashArray;

			NameScope.SetNameScope(Menu, this);
		}

		public void RefreshPoints()
		{
			if (From == null || To == null)
			{
				this.Visibility = Visibility.Hidden;
				return;
			}

			Line.Points.Clear();

			Point start;
			Point leftBottom;
			Point rightBottom;

			double thickness = Line.StrokeThickness;

			if (To.Center.Y - thickness> From.BottomCenter.Y)
			{
				start = new Point(From.BottomCenter.X, From.BottomCenter.Y);
				leftBottom = new Point(To.LeftCenter.X, To.Center.Y - thickness);
				rightBottom = new Point(To.RightCenter.X, To.Center.Y - thickness);
			}
			else if (From.Center.Y - _realThickness > To.BottomCenter.Y)
			{
				start = new Point(To.BottomCenter.X, To.BottomCenter.Y);
				leftBottom = new Point(From.LeftCenter.X, From.Center.Y - thickness);
				rightBottom = new Point(From.RightCenter.X, From.Center.Y - thickness);
			}//don't draw triangle if both nodes have the same level
			else
			{
				this.Visibility = Visibility.Collapsed;
				return;
			}

			this.Visibility = Visibility.Visible;

		    List<Point> list = new[] {start, leftBottom, rightBottom, start}.ToList();
		    foreach (var point in list)
		    {
		        Line.Points.Add(point);
		    }
            InvalidateAdorners();
		}

		public event Action<object, DeleteBinderEventArgs> Delete;

		protected virtual void OnDelete(object sender, DeleteBinderEventArgs e)
		{
			Action<object, DeleteBinderEventArgs> handler = Delete;
			if (handler != null) handler(sender, e);
		}

		private void DeleteMenuItem_Click(object sender, RoutedEventArgs e)
		{
			OnDelete(sender, new DeleteBinderEventArgs(this));
		}

		private bool ValidateDoubleCollection(string arg)
		{
			return ValidatableTextBox.ValidateDoubleCollection(arg);
		}

		private bool ValidateDouble(string arg)
		{
			return ValidatableTextBox.ValidateDouble(arg);
		}

		#region INameScope Members

		readonly Dictionary<string, object> _items = new Dictionary<string, object>();

		object INameScope.FindName(string name)
		{
			return _items[name];
		}

		void INameScope.RegisterName(string name, object scopedElement)
		{
			_items.Add(name, scopedElement);
		}

		void INameScope.UnregisterName(string name)
		{
			_items.Remove(name);
		}

		#endregion

		#region Implementation of IPrintable

		public bool IsBeingPrinted
		{
			get { return (bool)GetValue(IsBeingPrintedProperty); }
			set
			{
				SetValue(IsBeingPrintedProperty, value);
				if (value && _thicknessChanged)
					NormalizeThickness();
			}
		}

		// Using a DependencyProperty as the backing store for IsBeingPrinted.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty IsBeingPrintedProperty =
			DependencyProperty.Register("IsBeingPrinted", typeof(bool), typeof(TriangleLink), new UIPropertyMetadata(false));

		#endregion

		#region Implementation of IXmlSerializable

		/// <summary>
		/// This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
		/// </summary>
		/// <returns>
		/// An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method.
		/// </returns>
		public XmlSchema GetSchema()
		{
			return null;
		}

		/// <summary>
		/// Generates an object from its XML representation.
		/// </summary>
		/// <param name="reader">The <see cref="T:System.Xml.XmlReader"/> stream from which the object is deserialized. </param>
		public void ReadXml(XmlReader reader)
		{
			XDocument xdoc = XDocument.Load(reader);
			var root = xdoc.Root;

			Color color = (Color)XamlReader.Parse(root.Element("Color").Value);
			double thickness = double.Parse(root.Element("Thickness").Value.Replace(",", "."), CultureInfo.InvariantCulture);
			DoubleCollection dashStyle = DoubleCollection.Parse(root.Element("DashStyle").Value.Replace(",", "."));

			Initialize(color, thickness, dashStyle);
		}

		/// <summary>
		/// Converts an object into its XML representation.
		/// </summary>
		/// <param name="writer">The <see cref="T:System.Xml.XmlWriter"/> stream to which the object is serialized. </param>
		public void WriteXml(XmlWriter writer)
		{
			writer.WriteElementString("Color", XamlWriter.Save(((SolidColorBrush)Line.Stroke).Color));
			writer.WriteElementString("Thickness", Line.StrokeThickness.ToString());
			writer.WriteElementString("DashStyle", Line.StrokeDashArray == null ? "" : Line.StrokeDashArray.ToString());
			writer.WriteElementString("From", From.Guid.ToString());
			writer.WriteElementString("To", To.Guid.ToString());
		}

		#endregion


		private double _realThickness;
		private bool _thicknessChanged;

		public double RealThickness
		{
			get { return _realThickness; }
			private set
			{
				_realThickness = value;
			}
		}

		protected override void OnMouseEnter(MouseEventArgs e)
		{
			base.OnMouseEnter(e);
			if (Line.StrokeDashArray != null && Line.StrokeDashArray.Count > 0) return;
			RealThickness = Line.StrokeThickness;
			_thicknessChanged = true;
			Line.StrokeThickness = Math.Max(Line.StrokeThickness + 2, 5);
		}

		protected override void OnMouseLeave(MouseEventArgs e)
		{
			base.OnMouseLeave(e);
			if (_thicknessChanged)
				NormalizeThickness();
		}

		private void NormalizeThickness()
		{
			Line.StrokeThickness = RealThickness;
			_thicknessChanged = false;
		}

		private bool ValidateThickness(string arg)
		{
			if (!ValidatableTextBox.ValidateDouble(arg)) return false;
			double value;
			if (!double.TryParse(arg.Replace(",", "."), NumberStyles.Number, CultureInfo.InvariantCulture, out value))
				return false;

			return value <= ApplicationSettings.Default.MaxBinderThickness
				   && value >= ApplicationSettings.Default.MinBinderThickness;
		}

		private bool ParseDoubleCollection(string arg)
		{
			return ValidatableTextBox.ParseDoubleCollection(arg);
		}

		public Dictionary<string, object> MakeSnapshot()
		{
			var snapshot = new Dictionary<string, object>();
			snapshot.Add("Thickness", RealThickness);
			snapshot.Add("Dashes", Line.StrokeDashArray);
			snapshot.Add("Brush", Line.Stroke);

			return snapshot;
		}

		public void RestoreSnapshop(Dictionary<string, object> snapshot)
		{
			if (snapshot.ContainsKey("Thickness") && RealThickness != (double)snapshot["Thickness"])
				RealThickness = (double)snapshot["Thickness"];
			if (snapshot.ContainsKey("Dashes") && Line.StrokeDashArray != (DoubleCollection)snapshot["Dashes"])
				Line.StrokeDashArray = (DoubleCollection)snapshot["Dashes"];
			if (snapshot.ContainsKey("Brush") && Line.Stroke != (Brush)snapshot["Brush"])
				Line.Stroke = (Brush)snapshot["Brush"];
		}

		public bool DetectChanges(Dictionary<string, object> snapshot)
		{
			if (snapshot.ContainsKey("Thickness") && RealThickness != (double)snapshot["Thickness"])
				return true;
			if (snapshot.ContainsKey("Dashes") && Line.StrokeDashArray != (DoubleCollection)snapshot["Dashes"])
				return true;
			if (snapshot.ContainsKey("Brush") && Line.Stroke != (Brush)snapshot["Brush"])
				return true;
			return false;
		}

		public event Action<object, MenuChangesEventArgs> MenuOpened;

		protected virtual void OnMenuOpened(object sender, MenuChangesEventArgs e)
		{
			Action<object, MenuChangesEventArgs> handler = MenuOpened;
			if (handler != null) handler(sender, e);
		}

		public event Action<object, MenuChangesEventArgs> MenuClosed;

		protected virtual void OnMenuClosed(object sender, MenuChangesEventArgs e)
		{
			Action<object, MenuChangesEventArgs> handler = MenuClosed;
			if (handler != null) handler(sender, e);
		}

		private void Menu_OnOpened(object sender, RoutedEventArgs e)
		{
			OnMenuOpened(this, new MenuChangesEventArgs(this));
		}

		private void Menu_OnClosed(object sender, RoutedEventArgs e)
		{
			OnMenuClosed(this, new MenuChangesEventArgs(this));
		}

	    public bool Intersects(Rect selectionRect)
	    {
            var geometry = new RectangleGeometry(selectionRect);
	        var points = Line.Points;
	        var startPoint = points[0];
	        var lines = new List<LineSegment>();
	        for (int i = 1; i < points.Count; i++)
	        {
	            lines.Add(new LineSegment(points[i], true));
	        }
            Geometry polyline = new PathGeometry(new[]{new PathFigure(startPoint, lines, true)});
            var intersection = geometry.FillContainsWithDetail(polyline);

            return intersection != IntersectionDetail.Empty;
	    }

	    public bool IsSelected
        {
            get { return (bool)GetValue(IsSelectedProperty); }
            set
            {
                SetValue(IsSelectedProperty, value);
                InvalidateAdorners();
            }
        }

        private void InvalidateAdorners()
        {
            var adornerLayer = AdornerLayer.GetAdornerLayer(Line);
            if (adornerLayer == null) return;

            var adorners = adornerLayer.GetAdorners(Line);
            if (adorners == null) return;

            foreach (var adorner in adorners)
            {
                adorner.InvalidateVisual();
            }
        }

        public static readonly DependencyProperty IsSelectedProperty =
            DependencyProperty.Register("IsSelected", typeof(bool), typeof(TriangleLink), new PropertyMetadata(false));

        public event Action<object, SelectionEventArgs> Selected;

	    protected virtual void OnSelected(object arg1, SelectionEventArgs arg2)
	    {
	        Action<object, SelectionEventArgs> handler = Selected;
	        if (handler != null) handler(arg1, arg2);
	    }

	    public void InitializeSelectionAdorners()
        {
            var adornerLayer = AdornerLayer.GetAdornerLayer(Line);
            var adorner = new TriangleAdorner(Line);

            var binding = new Binding("IsSelected");
            binding.Source = this;

            adorner.SetBinding(EllipseAdorner.IsActiveProperty, binding);
            adornerLayer.Add(adorner);
        }

	    private void Line_OnLoaded(object sender, RoutedEventArgs e)
	    {
            InitializeSelectionAdorners();
	    }

	    private void Line_OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
	    {
	        OnSelected(this, new SelectionEventArgs(this));
	    }
	}
}
